# write by hashaki
# first edit on 2018/11/04   last change on 2018/12/03
# function:get data from mongoDB
from pymongo import MongoClient
from collections import OrderedDict
from datetime import datetime

class MongoFeed:
    def __init__(self,base,kwarg):
        '''base格式{'base_type':['1min','symbol'],'start':'20181103','end':'20181103'},
        kwarg格式{'btc':['5min',20],'symbol':['周期',前N根]},kwargs一定要为OrderDict结构，否则倍数与数据对不上'''

        self.base_type=base['base_type']
        self.start_time=base['start']
        self.end_time=base['end']

        self.client=MongoClient()
        self.kwargs=kwarg

        # 用于计算各个周期与基础周期之间的比值
        self.multiple={'1min':1,'5min':5,'15min':15,'30min':30,'1h':60,'4h':240,'12h':720}

    @classmethod
    def getMongoData(cls,base,kwarg=None):
        '''向外接口，分别返回{'base':1,'type2':5,...},{'base_1min':data1,'btc_5min':data2,...} '''
        tamp=cls(base,kwarg)
        multiple=tamp.returnMultiple()
        bar_data=tamp.returnKwargs()
        return multiple,bar_data

    # -----------------------------------------------------------------------------------
    # 下为处理数据
    def returnMultiple(self):
        '''返回{'type1':倍数1,'type2':倍数2...}'''
        if not self.kwargs:
            return None
        else:
            tamp=self.calculateMultiple()
            return tamp

    def returnKwargs(self):
        '''返回{'base_1min':Kbar1,'btc_5min':Kbar2,...}'''
        if not self.kwargs:
            base_data=self.cutBaseData()
            return {'base':base_data}
        else:
            result=OrderedDict()
            base_data=self.cutBaseData()
            otherData=self.cutOtherData()
            result['base']=base_data

            for i,k in zip(otherData,self.kwargs):
                time=self.kwargs[k][0]         # 周期
                tamp_string=k+'_'+time
                result[tamp_string]=i

            return result

    def cutBaseData(self):
        '''截取某段基础周期数据'''
        # TODO:把返回单个品种的数据换成多个周期的数据put进队列
        base=self.client[self.base_type[0]][self.base_type[1]]
        time={'date':{"$gte":self.start_time,"$lte":self.end_time}}

        return [{'time':i['date'],'volume':i['volume'],'open':i['open'],'high':i['high'],
        'low':i['low'],'close':i['close']} for i in base.find(time)]

    def cutOtherData(self):
        '''截取其他品种周期数据'''
        # TODO:多进程同时读取多周期数据,目前是基于数据库字段'date'，以后要换成datetime格式
        result=[]
        for i in self.kwargs:
            other=self.client[self.kwargs[i][0]][i] # 因为数据库结构是周期-品种,other是某个table了

            # 计算基于基础周期开始时间的前N根其他周期
            stander_time=other.find_one({"date":{"$gte":self.start_time}})
            n=self.multiple[self.kwargs[i]]*self.kwargs[i][1]    # N的数量
            n_time=self.calculateForeBar(stander_time,n)

            time={'data':{"$gte":n_time,"$lte":self.end_time}}

            tamp=[{'time':j['date'],'volume':j['volume'],'open':j['open'],
            'high':j['high'],'low':j['low'],'close':j['close']} for j in other.find(time)]

            result.append(tamp)
        return result

    def calculateMultiple(self):
        '''计算其他周期与基本K线之间的倍数，并返回到returnMultiple'''
        from math import floor

        base_multiple=self.multiple[self.base_type[0]]        # 基础线数值
        tamp=[]
        kwarg=[]
        tamp.append(1)  # 把基础周期插入表头，1的意思是base_multiple/base_multiple
        for i in self.kwargs:
            a=floor(self.multiple[self.kwargs[i][0]]/base_multiple)  # 保存其他周期的倍值数，以一分钟作为单位,再除以基础线倍值
            tamp.append(a)
            kwarg.append(self.kwargs[i][0])      # 周期类型列表,为了使用zip去迭代

        assert all(x>=1 for x in tamp)   # 保证其他周期必须长过基本周期

        del tamp[0]    # 删掉第一个元素，为了下面迭代时长度一致

        result=OrderedDict()
        result['base']=1  # 初始化,上面已经删掉了第一个，这里初始化就直接加上第一个
        for j,k in zip(kwarg,tamp):
            result[j]=k

        return result

    def calculateForeBar(self,time_,n):
        '''从最后的时间点，计算前N个时间单位的开始时间点'''
        from datetime import datetime
        from datetime import timedelta
        # TODO:要统一数据库时间格式才能完成这个，这是思路
        return time_-timedelta(minutes=-n).strftime('%Y-%m-%d %H:%M')

class MongoFeeds:
    def __init__(self,base,kwarg,q):
        '''base格式{'base':{'time':'1min','base_bars':['btc','eos','eth']},'start':'20181103','end':'20181103'},
        kwarg格式{'btc':['5min',20],'symbol':['周期',前N根]},kwargs一定要为OrderDict结构，否则倍数与数据对不上
        q是队列'''
        self.base_time=base['base']['time']
        self.base_bars=base['base']['base_bars']
        self.start_time=base['start']
        self.end_time=base['end']

        self.client=MongoClient()
        self.kwargs=kwarg
        self.q=q

        # 用于计算各个周期与基础周期之间的比值
        self.multiple={'1min':1,'5min':5,'15min':15,'30min':30,'1h':60,'4h':240,'12h':720,'1D':1440}

    @classmethod
    def getMongoData(cls,base,kwarg=None,q=None):
        '''向外接口，分别返回{'base':1,'type2':5,...},{'base_1min':{'A':dataA,'B':dataB,'C':dataC,...},'btc_5min':data2,...} '''
        tamp=cls(base,kwarg,q)
        multiple=tamp.returnMultiple()
        bar_data=tamp.returnKwargs()
        return multiple,bar_data

    @classmethod
    def mulMongoData(cls,q,base,kwarg=None):
        '''多品种数据读取接口,q是队列，读取的数据通过队列传到引擎,multiple返回{'base':1,'type2':5,...}
            从队列传{'A':dataA,'B':dataB,'C':dataC,...},
            其他周期{'1min':data1,'btc_5min':data2,...}'''
        # TODO:实现多品种多周期
        temp=cls(base,kwarg,q)
        multiple=temp.returnMultiple()
        temp.mulBaseData()
        return multiple,kwarg

    # -----------------------------------------------------------------------------------
    # 下为处理数据
    def returnMultiple(self):
        '''返回{'type1':倍数1,'type2':倍数2...}'''
        if not self.kwargs:
            return None
        else:
            tamp=self.calculateMultiple()
            return tamp

    def returnKwargs(self):
        '''返回{'base_1min':Kbar1,'btc_5min':Kbar2,...}'''
        if not self.kwargs:
            base_data=self.cutBaseData()
            return {'base':base_data}
        else:
            result=OrderedDict()
            base_data=self.cutBaseData()
            otherData=self.cutOtherData()
            result['base']=base_data

            for i,k in zip(otherData,self.kwargs):
                time=self.kwargs[k][0]         # 周期
                tamp_string=k+'_'+time
                result[tamp_string]=i

            return result

    def mulBaseData(self):
        '''多品种方法'''
        # TODO:一批一批地切数据到引擎
        time={'date':{"$gte":self.start_time,"$lte":self.end_time}}
        bars={}
        for i in self.base_bars:
            mul_base=self.client[self.base_time][i]
            bars[i]=[{'time':j['date'],'volume':j['volume'],'open':j['open'],'high':j['high'],
                'low':j['low'],'close':j['close']} for j in mul_base.find(time)]

        self.q.put(bars)

    def cutBaseData(self):
        '''截取某段基础周期数据,单品种方法'''
        base=self.client[self.base_time][self.base_bars[0]]
        time={'date':{"$gte":self.start_time,"$lte":self.end_time}}

        return [{'time':i['date'],'volume':i['volume'],'open':i['open'],'high':i['high'],
        'low':i['low'],'close':i['close']} for i in base.find(time)]

    def cutOtherData(self):
        '''截取其他品种周期数据'''
        # TODO:多进程同时读取多周期数据,目前是基于数据库字段'date'，以后要换成datetime格式
        result=[]
        for i in self.kwargs:
            other=self.client[self.kwargs[i][0]][i] # 因为数据库结构是周期-品种,other是某个table了

            # 计算基于基础周期开始时间的前N根其他周期
            stander_time=other.find_one({"date":{"$gte":self.start_time}})
            n=self.multiple[self.kwargs[i]]*self.kwargs[i][1]    # N的数量
            n_time=self.calculateForeBar(stander_time,n)

            time={'data':{"$gte":n_time,"$lte":self.end_time}}

            tamp=[{'time':j['date'],'volume':j['volume'],'open':j['open'],
            'high':j['high'],'low':j['low'],'close':j['close']} for j in other.find(time)]

            result.append(tamp)
        return result

    def calculateMultiple(self):
        '''计算其他周期与基本K线之间的倍数，并返回到returnMultiple'''
        from math import floor

        base_multiple=self.multiple[self.base_time]        # 基础线数值
        tamp=[]
        kwarg=[]
        tamp.append(1)  # 把基础周期插入表头，1的意思是base_multiple/base_multiple
        for i in self.kwargs:
            a=floor(self.multiple[self.kwargs[i][0]]/base_multiple)  # 保存其他周期的倍值数，以一分钟作为单位,再除以基础线倍值
            tamp.append(a)
            kwarg.append(self.kwargs[i][0])      # 周期类型列表,为了使用zip去迭代

        assert all(x>=1 for x in tamp)   # 保证其他周期必须长过基本周期

        del tamp[0]    # 删掉第一个元素，为了下面迭代时长度一致

        result=OrderedDict()
        result['base']=1  # 初始化,上面已经删掉了第一个，这里初始化就直接加上第一个
        for j,k in zip(kwarg,tamp):
            result[j]=k

        return result

    def calculateForeBar(self,time_,n):
        '''从最后的时间点，计算前N个时间单位的开始时间点'''
        from datetime import timedelta
        # TODO:要统一数据库时间格式才能完成这个，这是思路
        return time_-timedelta(minutes=-n).strftime('%Y-%m-%d %H:%M')

class MongoFeedFromBitQuant:
    def __init__(self,base,kwarg,q):
        '''base格式{'base':{'time':'1min','base_bars':['btc','eos','eth']},'start':'20181103','end':'20181103'},
        kwarg格式{'btc':['5min',20],'symbol':['周期',前N根]},kwargs一定要为OrderDict结构，否则倍数与数据对不上
        q是队列'''
        self.base_time=base['base']['time']
        self.base_bars=base['base']['base_bars']
        self.start_time=datetime.strptime(base['start'],'%Y%m%d')
        self.end_time=datetime.strptime(base['end'],'%Y%m%d')

        self.client=MongoClient('192.168.101.189',27017)
        self.kwargs=kwarg
        self.q=q

        # 用于计算各个周期与基础周期之间的比值
        self.multiple={'1min':1,'5min':5,'15min':15,'30min':30,'1h':60,'3h':180,'6h':360,'12h':720,'1D':1440}

    @classmethod
    def mulMongoData(cls,q,base,kwarg=None):
        '''多品种数据读取接口,q是队列，读取的数据通过队列传到引擎,multiple返回{'base':1,'type2':5,...}
            从队列传{'A':dataA,'B':dataB,'C':dataC,...},
            其他周期{'1min':data1,'btc_5min':data2,...}'''
        # TODO:实现多品种多周期
        temp=cls(base,kwarg,q)
        multiple=temp.returnMultiple()
        temp.mulBaseData()
        return multiple,kwarg

    # -----------------------------------------------------------------------------------
    # 下为处理数据
    def returnMultiple(self):
        '''返回{'type1':倍数1,'type2':倍数2...}'''
        if not self.kwargs:
            return None
        else:
            tamp=self.calculateMultiple()
            return tamp

    def returnKwargs(self):
        '''返回{'base_1min':Kbar1,'btc_5min':Kbar2,...}'''
        if not self.kwargs:
            return 
        else:
            otherData=self.cutOtherData()


    def mulBaseData(self):
        '''多品种方法,单条数据{'symbol': 'bitfinex_btc_usd', 'frequency': '1min', 'start_time': datetime.datetime(2013, 4, 11, 4, 29), 
        'end_time': datetime.datetime(2013, 4, 11, 4, 30), 'open': 177, 'high': 177, 'low': 177, 'close': 177, 'volume': 42.67855001, 
        'amount': 0.0, 'openInterest': 0.0}'''
        # TODO:一批一批地切数据到引擎
        time={'start_time':{"$gte":self.start_time,"$lte":self.end_time}}
        bars={}

        for i in self.base_bars:
            mul_base=self.client[self.base_time][i]
            bars[i]=[{'time':j['start_time'],'volume':j['volume'],'open':j['open'],'high':j['high'],
                'low':j['low'],'close':j['close']} for j in mul_base.find(time)]

        self.q.put(bars)

    def cutOtherData(self):
        '''截取其他品种周期数据'''
        # TODO:多进程同时读取多周期数据,目前是基于数据库字段'date'，以后要换成datetime格式
        result=[]
        for i in self.kwargs:
            other=self.client[self.kwargs[i][0]][i] # 因为数据库结构是周期-品种,other是某个table了

            # 计算基于基础周期开始时间的前N根其他周期
            stander_time=other.find_one({"date":{"$gte":self.start_time}})
            n=self.multiple[self.kwargs[i]]*self.kwargs[i][1]    # N的数量
            n_time=self.calculateForeBar(stander_time,n)

            time={'data':{"$gte":n_time,"$lte":self.end_time}}

            tamp=[{'time':j['date'],'volume':j['volume'],'open':j['open'],
            'high':j['high'],'low':j['low'],'close':j['close']} for j in other.find(time)]

            result.append(tamp)
        return result

    def calculateMultiple(self):
        '''计算其他周期与基本K线之间的倍数，并返回到returnMultiple'''
        from math import floor

        base_multiple=self.multiple[self.base_time]        # 基础线数值
        tamp=[]
        kwarg=[]
        tamp.append(1)  # 把基础周期插入表头，1的意思是base_multiple/base_multiple
        for i in self.kwargs:
            a=floor(self.multiple[self.kwargs[i][0]]/base_multiple)  # 保存其他周期的倍值数，以一分钟作为单位,再除以基础线倍值
            tamp.append(a)
            kwarg.append(self.kwargs[i][0])      # 周期类型列表,为了使用zip去迭代

        assert all(x>=1 for x in tamp)   # 保证其他周期必须长过基本周期

        del tamp[0]    # 删掉第一个元素，为了下面迭代时长度一致

        result=OrderedDict()
        result['base']=1  # 初始化,上面已经删掉了第一个，这里初始化就直接加上第一个
        for j,k in zip(kwarg,tamp):
            result[j]=k

        return result

    def calculateForeBar(self,time_,n):
        '''从最后的时间点，计算前N个时间单位的开始时间点'''
        from datetime import timedelta
        # TODO:要统一数据库时间格式才能完成这个，这是思路
        return time_-timedelta(minutes=-n).strftime('%Y-%m-%d %H:%M')